/*
 * ManagedText.java
 *
 * Created on November 4, 2006, 6:41 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package edu.umn.cs5115.scheduler.framework.gui;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;

import edu.umn.cs5115.scheduler.framework.Document;
import edu.umn.cs5115.scheduler.framework.ManagedData;
import edu.umn.cs5115.scheduler.framework.UndoManager;
import edu.umn.cs5115.scheduler.framework.Undoable;

/**
 * A text field that knows how to undo changes made to it.
 * It also sends notifications when it's text changes and responds to 
 * Note that since SWT's Text implementation is not supposed to be subclassed,
 * this class wraps a Text object.  You can get the Text object to use SWT's
 * normal methods on it.
 * @author grant
 */
public class ManagedText extends ManagedData
{
    /** The key identifying the text (string) attribute of this field. */
    public static final String TEXT_KEY = "text";
    
    private UndoManager undoManager;
    private Document document;
    private Text text;
    
    /**
     * 
     * Creates a new instance of ManagedText and a new Text for it to wrap.
     * @param parent The parent component of the Text.
     * @param style The style constants for the Text
     * @param document the Document this ManagedText is in.
     * @see org.eclipse.swt.widgets.Text#Text(Composite, int)
     */
    public ManagedText(Composite parent, int style, Document document)
    {
        super(document);
        setText(new Text(parent, style));
        this.document = document;
        undoManager = document.getUndoManager();
    }
    
    /**
     * Creates a new instance of ManagedText wrapped around an existing Text.
     * 
     * @param text The SWT Text widget to wrap
     * @param document The Document this ManagedText will use an undo manager for
     * to record changes made during editing.
     */
    public ManagedText(Text text, Document document)
    {
        super(document);
        setText(text);
        this.document = document;
        undoManager = document.getUndoManager();
    }
    
    /**
     * Return the SWT Text widget this ManagedText wraps.
     * @return The text this ManagedText wraps.
     */
    public Text getTextField()
    {
        return text;
    }
    
    /**
     * Get the current string that is in the text managed by this object.
     * @return The value of the managed text field.
     */
    public String getText()
    {
        return text.getText();
    }
    
    /**
     * Set the value of the managed text field.
     * @param string The new string to be displayed by the text field managed
     * by this object.  If it is null, the text is set to "".
     */
    public void setText(String string)
    {
        if (string == null)
            string = "";
        if (!string.equals(getText()))
        {
            text.setText(string);
            didChangeValueForKey(TEXT_KEY);
        }
    }

    /**
     * Sets the text that is being wrapped by this component.  This sets up
     * listeners to register undoables when the text changes.
     * @param text The component to wrap
     */
    private void setText(Text text)
    {
        this.text = text;
        Listener listener = new Listener();
        text.addFocusListener(listener);
        text.addModifyListener(listener);
        text.addKeyListener(listener);
    }
    
    /**
     * Handles receiving notifications and registering for undo when the field
     * gets typed in.
     */
    private class Listener implements FocusListener, ModifyListener, KeyListener
    {
        private String currentText;
        private boolean modifiedText = false;
        private boolean typing;
        private boolean deleting;
        private static final int BACKSPACE_KEY = 8;
        private static final int ENTER_KEY = 3;
        private static final int RETURN_KEY = 13;
        private int editingSession = 0;
        
        public Listener()
        {
            currentText = text.getText();
        }
        
        /**
         * Focus gained event - 
         * Make sure we know what the current text is.
         */
        public void focusGained(FocusEvent e)
        {
            currentText = text.getText();
            typing = false;
            deleting = false;
        }

        /**
         * Focus lost event - end our extra grouping, if it's there.
         */
        public void focusLost(FocusEvent e)
        {
            if (undoManager.getUndoGroupLevel() > 1)
                undoManager.endUndoGrouping();
            editingSession++;
            sendNotification();
        }
        
        /** Send the change notification if the text has been modified. */
        private void sendNotification()
        {
            if (modifiedText)
            {
                didChangeValueForKey(TEXT_KEY);
                modifiedText = false;
            }
        }

        /**
         * Text modified - save the current text so we can undo.
         */
        public void modifyText(ModifyEvent e)
        {
            final String oldText = currentText;
            final int currentSession = editingSession;
            currentText = text.getText();
            undoManager.registerUndo(new Undoable()
            {
                public void undo()
                {
                    text.setText(oldText);
                }
                
                public boolean isValid()
                {
                    return !text.isDisposed() && (editingSession == currentSession);
                }
            });
        }

        /**
         * Key down - determine if we're typing or deleting text, so we can
         * seperate those events out.
         */
        public void keyPressed(KeyEvent e)
        {
            if (e.character != 0)
            {
                int key = e.keyCode;
                if (undoManager.getUndoGroupLevel() <= 1)
                {
                    deleting = false;
                    typing = false;
                }
                if (key == ENTER_KEY || key == RETURN_KEY)
                {
                    sendNotification();
                } else if (key == BACKSPACE_KEY || key == SWT.DEL){
                    modifiedText = true;
                    if (!deleting)
                    {
                        while (undoManager.getUndoGroupLevel() > 0)
                            undoManager.endUndoGrouping();
                        deleting = true;
                        typing = false;
                        undoManager.beginUndoGrouping();
                        undoManager.beginUndoGrouping();
                        undoManager.setActionName("Delete Text");
                    }
                } else {
                    modifiedText = true;
                    if (!typing)
                    {
                        while (undoManager.getUndoGroupLevel() > 0)
                            undoManager.endUndoGrouping();
                        typing = true;
                        deleting = false;
                        undoManager.beginUndoGrouping();
                        undoManager.beginUndoGrouping();
                        undoManager.setActionName("Typing");
                    }
                }
            }
        }

        /** ignored. */
        public void keyReleased(KeyEvent e) {}
    }
}

